﻿using System;
using System.Runtime.InteropServices;
using System.Threading;
using DirectShow;
using DirectShow.BaseClasses;
using Sonic;

namespace CamPlug.Camera
{
	[ComVisible(false)]
	public class CamPlugStream : SourceStream,
		IAMStreamControl, IKsPropertySet, IAMPushSource, IAMLatency, IAMStreamConfig, IAMBufferNegotiation
	{
		public static HRESULT E_PROP_SET_UNSUPPORTED { get { unchecked { return (HRESULT)0x80070492; } } }
		public static HRESULT E_PROP_ID_UNSUPPORTED { get { unchecked { return (HRESULT)0x80070490; } } }

		protected object PinSyncRoot = new object();
		protected object TimeLockSyncRoot = new object();
		protected long Start = 0;
		protected long StreamOffset = 0;
		protected long StreamOffsetMax = -1;
		protected long m_StartAt = -1;
		protected long m_StopAt = -1;
		protected int StartCookie = 0;
		protected int StopCookie = 0;
		protected bool ShouldFlush = false;
		protected bool StartNotified = false;
		protected bool StopNotified = false;
		protected AllocatorProperties AllocatorProperties = null;
		protected IReferenceClockImpl ReferenceClock = null;
		protected int ClockAdviseToken = 0;
		protected Semaphore ClockSemaphore = null;
		protected long ClockStart = 0;
		protected long ClockStop = 0;

		public CamPlugStream(string deviceName, BaseSourceFilter filter)
			: base(deviceName, filter)
		{
			m_mt.majorType = Guid.Empty;

			GetMediaType(0, ref m_mt);
		}

		public override int SetMediaType(AMMediaType mediaType)
		{
			if (mediaType == null) 
				return E_POINTER;
			if (mediaType.formatPtr == IntPtr.Zero) 
				return VFW_E_INVALIDMEDIATYPE;
			HRESULT result = (HRESULT)CheckMediaType(mediaType);
			if (result.Failed) 
				return result;
			result = (HRESULT)base.SetMediaType(mediaType);
			if (result.Failed) 
				return result;
			if (AllocatorProperties != null)
				SuggestAllocatorProperties(AllocatorProperties);
			return (m_Filter as CamPlugFilter).SetMediaType(mediaType);
		}

		public override int CheckMediaType(AMMediaType mediaType)
		{
			return (m_Filter as CamPlugFilter).CheckMediaType(mediaType);
		}

		public override int GetMediaType(int position, ref AMMediaType mediaType)
		{
			return (m_Filter as CamPlugFilter).GetMediaType(position, ref mediaType);
		}

		public override int DecideBufferSize(ref IMemAllocatorImpl memoryAllocator, ref AllocatorProperties allocatorProperties)
		{
			if (!IsConnected) 
				return VFW_E_NOT_CONNECTED;
			AllocatorProperties newAllocatorProperties = new AllocatorProperties();
			HRESULT result = (HRESULT)GetAllocatorProperties(newAllocatorProperties);
			if (SUCCEEDED(result) 
					&& newAllocatorProperties.cBuffers <= allocatorProperties.cBuffers 
					&& newAllocatorProperties.cbBuffer <= allocatorProperties.cbBuffer 
					&& newAllocatorProperties.cbAlign == allocatorProperties.cbAlign
					)
			{
				AllocatorProperties actualAllocatorProperties = new AllocatorProperties();
				result = (HRESULT)memoryAllocator.SetProperties(allocatorProperties, actualAllocatorProperties);
				if (SUCCEEDED(result)) 
					return result;
			}
			return (m_Filter as CamPlugFilter).DecideBufferSize(ref memoryAllocator, ref allocatorProperties);
		}

		public override int Active()
		{
			Start = 0;
			StartNotified = false;
			StopNotified = false;
			{
				lock (m_Filter.FilterLock)
				{
					ReferenceClock = m_Filter.Clock;
					if (ReferenceClock.IsValid)
					{
						ReferenceClock._AddRef();
						ClockSemaphore = new Semaphore(0, 0x7FFFFFFF);
					}
				}
			}
			return base.Active();
		}

		public override int Inactive()
		{
			HRESULT result = (HRESULT)base.Inactive();
			if (ReferenceClock != null)
			{
				if (ClockAdviseToken != 0)
				{
					ReferenceClock.Unadvise(ClockAdviseToken);
					ClockAdviseToken = 0;
				}
				ReferenceClock._Release();
				ReferenceClock = null;
				if (ClockSemaphore != null)
				{
					ClockSemaphore.Close();
					ClockSemaphore = null;
				}
			}
			return result;
		}

		public override int FillBuffer(ref IMediaSampleImpl sample)
		{
			{
				AMMediaType mediaType;
				if (S_OK == sample.GetMediaType(out mediaType))
				{
					if (FAILED(SetMediaType(mediaType)))
					{
						ASSERT(false);
						sample.SetMediaType(null);
					}
					mediaType.Free();
				}
			}
			long start, stop;
			HRESULT result = NOERROR;
			long latency;
			if (FAILED(GetLatency(out latency)))
				latency = UNITS / 30;

			bool shouldDeliver = false;
			do
			{
				if (ClockAdviseToken == 0)
				{
					ReferenceClock.GetTime(out ClockStart);
					result = (HRESULT)ReferenceClock.AdvisePeriodic(ClockStart + latency, latency, ClockSemaphore.Handle, out ClockAdviseToken);
					result.Assert();
				}
				else
				{
					if (!ClockSemaphore.WaitOne())
						ASSERT(FALSE);
				}
				shouldDeliver = TRUE;
				start = Start;
				stop = Start + 1;
				sample.SetTime(start, stop);
				result = (HRESULT)(m_Filter as CamPlugFilter).FillBuffer(ref sample);
				if (FAILED(result) || S_FALSE == result) 
					return result;

				ReferenceClock.GetTime(out ClockStop);
				sample.GetTime(out start, out stop);

				if (latency > 0 && latency * 3 < ClockStop - ClockStart)
					ClockStop = ClockStart + latency;
				stop = start + (ClockStop - ClockStart);
				Start = stop;
				lock (PinSyncRoot)
				{
					start -= StreamOffset;
					stop -= StreamOffset;
				}
				sample.SetTime(start, stop);
				ClockStart = ClockStop;

				shouldDeliver = ((start >= 0) && (stop >= 0));

				if (shouldDeliver)
				{
					lock (PinSyncRoot)
						if (m_StartAt != -1)
						{
							if (m_StartAt > start)
								shouldDeliver = FALSE;
							else
							{
								if (StartCookie != 0 && !StartNotified)
								{
									StartNotified = TRUE;
									result = (HRESULT)m_Filter.NotifyEvent(EventCode.StreamControlStarted, Marshal.GetIUnknownForObject(this), (IntPtr)StartCookie);
									if (FAILED(result)) return result;
								}
							}
						}
					if (!shouldDeliver) 
						continue;
					if (m_StopAt != -1)
					{
						if (m_StopAt < start)
						{
							if (!StopNotified)
							{
								StopNotified = TRUE;
								if (StopCookie != 0)
								{
									result = (HRESULT)m_Filter.NotifyEvent(
										EventCode.StreamControlStopped, 
										Marshal.GetIUnknownForObject(this), 
										(IntPtr)StopCookie);
									if (FAILED(result)) 
										return result;
								}
								shouldDeliver = ShouldFlush;
							}
							else
								shouldDeliver = FALSE;
							// EOS
							if (!shouldDeliver) return S_FALSE;
						}
					}
				}
			}
			while (!shouldDeliver);

			return NOERROR;
		}

		#region IAMBufferNegotiation Members

		public int SuggestAllocatorProperties(AllocatorProperties pprop)
		{
			if (IsConnected) return VFW_E_ALREADY_CONNECTED;
			HRESULT hr = (HRESULT)(m_Filter as CamPlugFilter).SuggestAllocatorProperties(pprop);
			if (FAILED(hr))
			{
				AllocatorProperties = null;
				return hr;
			}
			if (AllocatorProperties == null)
			{
				AllocatorProperties = new AllocatorProperties();
				(m_Filter as CamPlugFilter).GetAllocatorProperties(AllocatorProperties);
			}
			if (pprop.cbBuffer != -1) AllocatorProperties.cbBuffer = pprop.cbBuffer;
			if (pprop.cbAlign != -1) AllocatorProperties.cbAlign = pprop.cbAlign;
			if (pprop.cbPrefix != -1) AllocatorProperties.cbPrefix = pprop.cbPrefix;
			if (pprop.cBuffers != -1) AllocatorProperties.cBuffers = pprop.cBuffers;
			return NOERROR;
		}

		public int GetAllocatorProperties(AllocatorProperties pprop)
		{
			if (pprop == null) return E_POINTER;
			if (AllocatorProperties != null)
			{
				pprop.cbAlign = AllocatorProperties.cbAlign;
				pprop.cbBuffer = AllocatorProperties.cbBuffer;
				pprop.cbPrefix = AllocatorProperties.cbPrefix;
				pprop.cBuffers = AllocatorProperties.cBuffers;
				return NOERROR;
			}
			if (IsConnected)
			{
				HRESULT hr = (HRESULT)Allocator.GetProperties(pprop);
				if (SUCCEEDED(hr) && pprop.cBuffers > 0 && pprop.cbBuffer > 0) return hr;
			}
			return (m_Filter as CamPlugFilter).GetAllocatorProperties(pprop);
		}

		#endregion

		#region IAMStreamConfig Members

		public int SetFormat(AMMediaType pmt)
		{
			if (m_Filter.IsActive) return VFW_E_WRONG_STATE;
			HRESULT hr;
			AMMediaType _newType = new AMMediaType(pmt);
			AMMediaType _oldType = new AMMediaType(m_mt);
			hr = (HRESULT)CheckMediaType(_newType);
			if (FAILED(hr)) return hr;
			m_mt.Set(_newType);
			if (IsConnected)
			{
				hr = (HRESULT)Connected.QueryAccept(_newType);
				if (SUCCEEDED(hr))
				{
					hr = (HRESULT)m_Filter.ReconnectPin(this, _newType);
					if (SUCCEEDED(hr))
					{
						hr = (HRESULT)(m_Filter as CamPlugFilter).SetMediaType(_newType);
					}
					else
					{
						m_mt.Set(_oldType);
						m_Filter.ReconnectPin(this, _oldType);
					}
				}
			}
			else
			{
				hr = (HRESULT)(m_Filter as CamPlugFilter).SetMediaType(_newType);
			}
			return hr;
		}

		public int GetFormat(out AMMediaType pmt)
		{
			pmt = new AMMediaType(m_mt);
			return NOERROR;
		}

		public int GetNumberOfCapabilities(IntPtr piCount, IntPtr piSize)
		{
			int iCount;
			int iSize;
			HRESULT hr = (HRESULT)(m_Filter as CamPlugFilter).GetNumberOfCapabilities(out iCount, out iSize);
			if (hr.Failed) return hr;
			if (piCount != IntPtr.Zero)
			{
				Marshal.WriteInt32(piCount, iCount);
			}
			if (piSize != IntPtr.Zero)
			{
				Marshal.WriteInt32(piSize, iSize);
			}
			return hr;
		}

		public int GetStreamCaps(int iIndex, IntPtr ppmt, IntPtr pSCC)
		{
			AMMediaType pmt;
			VideoStreamConfigCaps _caps;
			HRESULT hr = (HRESULT)(m_Filter as CamPlugFilter).GetStreamCaps(iIndex, out pmt, out _caps);
			if (hr != S_OK) return hr;

			if (ppmt != IntPtr.Zero)
			{
				IntPtr _ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(pmt));
				Marshal.StructureToPtr(pmt, _ptr, true);
				Marshal.WriteIntPtr(ppmt, _ptr);
			}
			if (pSCC != IntPtr.Zero)
			{
				Marshal.StructureToPtr(_caps, pSCC, false);
			}
			return hr;
		}

		#endregion

		#region IAMPushSource Members

		public int GetPushSourceFlags(out AMPushSourceFlags pFlags)
		{
			pFlags = AMPushSourceFlags.None;
			return NOERROR;
		}

		public int SetPushSourceFlags(AMPushSourceFlags Flags)
		{
			return E_NOTIMPL;
		}

		public int SetStreamOffset(long rtOffset)
		{
			lock (PinSyncRoot)
			{
				StreamOffset = rtOffset;
				if (StreamOffset > StreamOffsetMax) StreamOffsetMax = StreamOffset;
			}
			return NOERROR;
		}

		public int GetStreamOffset(out long prtOffset)
		{
			prtOffset = StreamOffset;
			return NOERROR;
		}

		public int GetMaxStreamOffset(out long prtMaxOffset)
		{
			prtMaxOffset = 0;
			if (StreamOffsetMax == -1)
			{
				HRESULT hr = (HRESULT)GetLatency(out StreamOffsetMax);
				if (FAILED(hr)) return hr;
				if (StreamOffsetMax < StreamOffset) StreamOffsetMax = StreamOffset;
			}
			prtMaxOffset = StreamOffsetMax;
			return NOERROR;
		}

		public int SetMaxStreamOffset(long rtMaxOffset)
		{
			if (rtMaxOffset < StreamOffset) return E_INVALIDARG;
			StreamOffsetMax = rtMaxOffset;
			return NOERROR;
		}

		#endregion

		#region IKsPropertySet Members

		public int Set(Guid guidPropSet, int dwPropID, IntPtr pInstanceData, int cbInstanceData, IntPtr pPropData, int cbPropData)
		{
			return E_NOTIMPL;
		}

		public int Get(Guid guidPropSet, int dwPropID, IntPtr pInstanceData, int cbInstanceData, IntPtr pPropData, int cbPropData, out int pcbReturned)
		{
			pcbReturned = Marshal.SizeOf(typeof(Guid));
			if (guidPropSet != PropSetID.Pin)
			{
				return E_PROP_SET_UNSUPPORTED;
			}
			if (dwPropID != (int)AMPropertyPin.Category)
			{
				return E_PROP_ID_UNSUPPORTED;
			}
			if (pPropData == IntPtr.Zero)
			{
				return NOERROR;
			}
			if (cbPropData < Marshal.SizeOf(typeof(Guid)))
			{
				return E_UNEXPECTED;
			}
			Marshal.StructureToPtr(PinCategory.Capture, pPropData, false);
			return NOERROR;
		}

		public int QuerySupported(Guid guidPropSet, int dwPropID, out KSPropertySupport pTypeSupport)
		{
			pTypeSupport = KSPropertySupport.Get;
			if (guidPropSet != PropSetID.Pin)
			{
				return E_PROP_SET_UNSUPPORTED;
			}
			if (dwPropID != (int)AMPropertyPin.Category)
			{
				return E_PROP_ID_UNSUPPORTED;
			}
			return S_OK;
		}

		#endregion

		#region IAMStreamControl Members

		public int StartAt(DsLong ptStart, int dwCookie)
		{
			lock (PinSyncRoot)
			{
				if (ptStart != null && ptStart != MAX_LONG)
				{
					m_StartAt = ptStart;
					StartCookie = dwCookie;
				}
				else
				{
					m_StartAt = -1;
					StartCookie = 0;
				}
			}
			return NOERROR;
		}

		public int StopAt(DsLong ptStop, bool bSendExtra, int dwCookie)
		{
			lock (PinSyncRoot)
			{
				if (ptStop != null && ptStop != MAX_LONG)
				{
					m_StopAt = ptStop;
					ShouldFlush = bSendExtra;
					StopCookie = dwCookie;
				}
				else
				{
					m_StopAt = -1;
					ShouldFlush = false;
					StopCookie = 0;
				}
			}
			return NOERROR;
		}

		public int GetInfo(out AMStreamInfo pInfo)
		{
			lock (PinSyncRoot)
			{
				pInfo = new AMStreamInfo();
				pInfo.dwFlags = AMStreamInfoFlags.None;

				if (Start < m_StartAt)
				{
					pInfo.dwFlags = pInfo.dwFlags | AMStreamInfoFlags.Discarding;
				}
				if (m_StartAt != -1)
				{
					pInfo.dwFlags = pInfo.dwFlags | AMStreamInfoFlags.StartDefined;
					pInfo.tStart = m_StartAt;
					pInfo.dwStartCookie = StartCookie;
				}
				if (m_StopAt != -1)
				{
					pInfo.dwFlags = pInfo.dwFlags | AMStreamInfoFlags.StopDefined;
					pInfo.tStop = m_StopAt;
					pInfo.dwStopCookie = StopCookie;
				}
				if (ShouldFlush) pInfo.dwFlags = pInfo.dwFlags | AMStreamInfoFlags.StopSendExtra;
			}
			return NOERROR;
		}

		#endregion

		#region IAMLatency Members

		public int GetLatency(out long prtLatency)
		{
			return (m_Filter as CamPlugFilter).GetLatency(out prtLatency);
		}

		#endregion
	}

}
